package tjs.study.notes.dotest.proxy.spring.springLoad;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import tjs.study.notes.dotest.TjsStudyNotesApplication;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;

/**
 * Created by Nicky.Tang on 2021/9/28 5:07 下午
 *
 * @since 02.12.10
 */
@RestController
@RequestMapping
public class ReloadController implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    private Method getMappingForMethod;
    private Method doDefineClass;
    private DefaultListableBeanFactory defaultListableBeanFactory;
    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * 重新编译class前，需要先移除旧的
     */
    @RequestMapping("/remove")
    public String remove(String beanClassName) throws Exception {
        Class<?> controllerClass = Class.forName(beanClassName);
        //移除旧的requestmapping映射
        for (Method declaredMethod : controllerClass.getDeclaredMethods()) {
            RequestMappingInfo requestMappingInfo = (RequestMappingInfo) this.getMappingForMethod.invoke(this.requestMappingHandlerMapping, declaredMethod, controllerClass);
            this.requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
        }
        //移除ioc中的bean
        String beanName = applicationContext.getBeanNamesForType(controllerClass)[0];
        this.defaultListableBeanFactory.removeBeanDefinition(beanName);
        return "remove success !";
    }

    @RequestMapping("/reload")
    public String reload(String beanClassName) throws Exception {
        // 重新装载新的class到classload
        byte[] newClassBytes = loadClassData(beanClassName);
        Class<?> controllerClass = (Class<?>) doDefineClass.invoke(applicationContext.getClassLoader(), beanClassName, newClassBytes, 0, newClassBytes.length, null);

        // 重新注册ioc中的bean
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(controllerClass);
        String beanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(beanDefinitionBuilder.getBeanDefinition(), this.defaultListableBeanFactory);
        this.defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());

        // 重新注册requestmapping映射
        Object bean = applicationContext.getBean(beanName);
        for (Method declaredMethod : bean.getClass().getDeclaredMethods()) {
            RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, declaredMethod, UserTestController.class);
            this.requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, declaredMethod);
        }

        return "reload success !";
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        try {
            this.getMappingForMethod = this.requestMappingHandlerMapping.getClass().getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
            this.getMappingForMethod.setAccessible(true);

            this.defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();

//            this.doDefineClass = this.applicationContext.getClassLoader().getClass().getSuperclass().getSuperclass().getDeclaredMethod("doDefineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class);
//            this.doDefineClass.setAccessible(true);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

    private byte[] loadClassData(String name) {
        try {
            // 传进来是带包名的
            name = name.replace(".", "//");
            FileInputStream inputStream = new FileInputStream(new File(TjsStudyNotesApplication.work_space_pre + "/target/classes/" + name + ".class"));
            // 定义字节数组输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = inputStream.read()) != -1) {
                baos.write(b);
            }
            inputStream.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
